#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
葵崽专属管理器 - 简洁稳定版
版本: 3.0.1
作者: 向日葵
功能: XRK-Yunzai机器人一键部署管理工具
"""

import sys
import os
import json
import platform
import subprocess
import threading
import time
import shutil
import zipfile
import requests
import traceback
from datetime import datetime
from pathlib import Path
from typing import Dict, List, Optional, Tuple, Callable
from dataclasses import dataclass, asdict

# GUI相关导入
import tkinter as tk
from tkinter import ttk, messagebox, filedialog
import customtkinter as ctk

# 尝试导入可选依赖
try:
    import psutil
except ImportError:
    psutil = None

# 设置CustomTkinter
ctk.set_appearance_mode("light")
ctk.set_default_color_theme("blue")

# ==================== 全局常量 ====================
APP_VERSION = "3.0.1"
APP_NAME = "葵崽专属管理器"

# 下载地址配置
DOWNLOAD_URLS = {
    "nodejs": {
        "x64": "https://nodejs.org/dist/v23.5.0/node-v23.5.0-x64.msi",
        "x86": "https://nodejs.org/dist/v23.5.0/node-v23.5.0-x86.msi"
    },
    "git": {
        "x64": "https://github.com/git-for-windows/git/releases/download/v2.47.1.windows.1/Git-2.47.1-64-bit.exe",
        "x86": "https://github.com/git-for-windows/git/releases/download/v2.47.1.windows.1/Git-2.47.1-32-bit.exe"
    },
    "redis": {
        "x64": "https://github.com/tporadowski/redis/releases/download/v5.0.14.1/Redis-x64-5.0.14.1.msi",
        "x86": "https://github.com/tporadowski/redis/releases/download/v5.0.14.1/Redis-x64-5.0.14.1.msi"
    },
    "ffmpeg": {
        "x64": "https://github.com/BtbN/FFmpeg-Builds/releases/download/latest/ffmpeg-master-latest-win64-gpl.zip",
        "x86": "https://github.com/BtbN/FFmpeg-Builds/releases/download/latest/ffmpeg-master-latest-win32-gpl.zip"
    },
    "napcat": {
        # NapCat使用同一个包，内部支持双架构
        "x64": "https://github.com/NapNeko/NapCatQQ/releases/download/v4.8.95/NapCat.Framework.Windows.Once.zip",
        "x86": "https://github.com/NapNeko/NapCatQQ/releases/download/v4.8.95/NapCat.Framework.Windows.Once.zip"
    }
}

# GitHub代理配置
GITHUB_PROXIES = [
    {"name": "gh-proxy.com", "url": "https://gh-proxy.com/"},
    {"name": "ghp.ci", "url": "https://ghp.ci/"},
    {"name": "ghproxy.net", "url": "https://mirror.ghproxy.com/"},
]

# 简洁配色方案
COLORS = {
    "primary": "#1E88E5",      # 主色调 - 蓝色
    "success": "#43A047",      # 成功 - 绿色
    "warning": "#FB8C00",      # 警告 - 橙色
    "danger": "#E53935",       # 危险 - 红色
    "bg": "#F5F5F5",          # 背景色
    "card": "#FFFFFF",        # 卡片背景
    "text": "#212121",        # 主文本
    "text_secondary": "#757575", # 次要文本
}

# 获取系统架构
SYSTEM_ARCH = "x64" if platform.machine().endswith('64') else "x86"

def center_window(window):
    """将窗口居中"""
    window.update_idletasks()
    width = window.winfo_width()
    height = window.winfo_height()
    x = (window.winfo_screenwidth() // 2) - (width // 2)
    y = (window.winfo_screenheight() // 2) - (height // 2)
    window.geometry(f'{width}x{height}+{x}+{y}')

@dataclass
class Config:
    """配置数据类"""
    last_install_path: str = ""
    yunzai_dir_name: str = "葵崽机器人"
    use_github_proxy: bool = True
    selected_github_proxy: str = GITHUB_PROXIES[0]["url"]
    skip_path_dialog: bool = False
    version: str = APP_VERSION

class PathManager:
    """路径管理器"""
    def __init__(self):
        self.base_path = ""
        self.yunzai_dir = ""
        self.project_path = ""
        self.software_path = ""
        self.napcat_path = ""
        self.log_path = ""
        
    def initialize(self, base_path: str, yunzai_dir_name: str):
        """初始化路径"""
        self.base_path = base_path
        self.yunzai_dir = os.path.join(base_path, yunzai_dir_name)
        
        # 简化的目录结构
        self.project_path = os.path.join(self.yunzai_dir, "XRK-Yunzai")
        self.software_path = os.path.join(self.yunzai_dir, "software")
        self.napcat_path = os.path.join(self.software_path, "NapCat")
        self.log_path = os.path.join(self.yunzai_dir, "logs")
        
        # 创建必要的目录
        for path in [self.yunzai_dir, self.software_path, self.napcat_path, self.log_path]:
            os.makedirs(path, exist_ok=True)
            
        self.log_file = os.path.join(self.log_path, f"install_{datetime.now().strftime('%Y%m%d')}.log")

class ConfigManager:
    """配置管理器"""
    def __init__(self):
        self.config_dir = Path.home() / ".yunzai_manager"
        self.config_file = self.config_dir / "config.json"
        self.config_dir.mkdir(exist_ok=True)
        
    def load(self) -> Config:
        """加载配置"""
        if self.config_file.exists():
            try:
                with open(self.config_file, 'r', encoding='utf-8') as f:
                    data = json.load(f)
                return Config(**data)
            except:
                pass
        return Config()
        
    def save(self, config: Config):
        """保存配置"""
        try:
            with open(self.config_file, 'w', encoding='utf-8') as f:
                json.dump(asdict(config), f, ensure_ascii=False, indent=2)
        except:
            pass

class EnvironmentChecker:
    """环境检测器"""
    @staticmethod
    def check_nodejs() -> Tuple[bool, str]:
        """检测Node.js"""
        try:
            result = subprocess.run(['node', '--version'], capture_output=True, text=True, shell=True)
            if result.returncode == 0:
                version = result.stdout.strip()
                ver_num = version.lstrip('v').split('.')
                if len(ver_num) >= 1 and int(ver_num[0]) >= 18:
                    return True, version
        except:
            pass
        return False, ""
            
    @staticmethod
    def check_pnpm() -> bool:
        """检测pnpm"""
        try:
            result = subprocess.run(['pnpm', '--version'], capture_output=True, text=True, shell=True)
            return result.returncode == 0
        except:
            return False
            
    @staticmethod
    def check_git() -> bool:
        """检测Git"""
        try:
            result = subprocess.run(['git', '--version'], capture_output=True, text=True, shell=True)
            return result.returncode == 0
        except:
            return False
            
    @staticmethod
    def check_redis() -> bool:
        """检测Redis"""
        try:
            result = subprocess.run(['sc', 'query', 'Redis'], capture_output=True, text=True, shell=True)
            return 'RUNNING' in result.stdout or 'STOPPED' in result.stdout
        except:
            return False
            
    @staticmethod
    def check_ffmpeg() -> bool:
        """检测FFmpeg"""
        try:
            result = subprocess.run(['ffmpeg', '-version'], capture_output=True, text=True, shell=True)
            return result.returncode == 0
        except:
            return False
            
    @staticmethod
    def check_napcat(napcat_path: str) -> bool:
        """检测NapCat"""
        return os.path.exists(os.path.join(napcat_path, "NapCatWinBootMain.exe"))
        
    @staticmethod
    def check_project(project_path: str) -> bool:
        """检测项目"""
        return os.path.exists(os.path.join(project_path, "app.js"))

class DownloadManager:
    """下载管理器"""
    def __init__(self, use_proxy: bool = True, proxy_url: str = ""):
        self.use_proxy = use_proxy
        self.proxy_url = proxy_url
        
    def download_with_progress(self, url: str, dest_path: str, 
                             progress_callback: Optional[Callable] = None) -> bool:
        """带进度的下载"""
        try:
            # 应用代理
            if self.use_proxy and 'github.com' in url and self.proxy_url:
                url = self.proxy_url + url
                
            # 确保目标目录存在
            os.makedirs(os.path.dirname(dest_path), exist_ok=True)
            
            # 下载文件
            response = requests.get(url, stream=True, timeout=180,
                                  headers={'User-Agent': 'Mozilla/5.0'})
            response.raise_for_status()
            
            total_size = int(response.headers.get('content-length', 0))
            downloaded = 0
            
            with open(dest_path, 'wb') as f:
                for chunk in response.iter_content(chunk_size=8192):
                    if chunk:
                        f.write(chunk)
                        downloaded += len(chunk)
                        if progress_callback and total_size > 0:
                            progress = (downloaded / total_size) * 100
                            progress_callback(progress, downloaded, total_size)
                            
            return True
        except Exception as e:
            print(f"下载失败: {e}")
            if os.path.exists(dest_path):
                os.remove(dest_path)
            return False

class PathSelectionDialog(ctk.CTkToplevel):
    """路径选择对话框 - 修复大小和滚动问题"""
    def __init__(self, parent, current_config: Config):
        super().__init__(parent)
        self.parent = parent
        self.config = current_config
        self.result = None
        
        self.title("选择安装位置")
        
        # 设置合适的窗口大小
        self.geometry("700x550")
        self.minsize(600, 500)
        
        # 设置模态
        self.transient(parent)
        self.grab_set()
        
        # 创建界面
        self._create_widgets()
        
        # 居中窗口
        center_window(self)
        
    def _create_widgets(self):
        """创建控件"""
        # 主框架
        main_frame = ctk.CTkFrame(self)
        main_frame.pack(fill="both", expand=True, padx=20, pady=20)
        
        # 标题
        title_label = ctk.CTkLabel(
            main_frame,
            text="选择安装位置",
            font=ctk.CTkFont(size=24, weight="bold")
        )
        title_label.pack(pady=(0, 20))
        
        # 路径选择框架
        path_frame = ctk.CTkFrame(main_frame)
        path_frame.pack(fill="x", pady=(0, 15))
        
        # 父目录选择
        ctk.CTkLabel(
            path_frame,
            text="安装目录：",
            font=ctk.CTkFont(size=14)
        ).pack(anchor="w", padx=15, pady=(10, 5))
        
        path_select_frame = ctk.CTkFrame(path_frame)
        path_select_frame.pack(fill="x", padx=15, pady=(0, 10))
        
        self.path_var = tk.StringVar(value=self.config.last_install_path or os.path.expanduser("~/Desktop"))
        self.path_entry = ctk.CTkEntry(
            path_select_frame,
            textvariable=self.path_var,
            state="readonly",
            font=ctk.CTkFont(size=13)
        )
        self.path_entry.pack(side="left", fill="x", expand=True, padx=(0, 10))
        
        ctk.CTkButton(
            path_select_frame,
            text="浏览",
            width=80,
            command=self._browse_folder
        ).pack(side="left")
        
        # 文件夹名称
        ctk.CTkLabel(
            path_frame,
            text="文件夹名称：",
            font=ctk.CTkFont(size=14)
        ).pack(anchor="w", padx=15, pady=(10, 5))
        
        self.name_var = tk.StringVar(value=self.config.yunzai_dir_name)
        self.name_entry = ctk.CTkEntry(
            path_frame,
            textvariable=self.name_var,
            font=ctk.CTkFont(size=13)
        )
        self.name_entry.pack(fill="x", padx=15, pady=(0, 15))
        
        # 记住选择
        self.remember_var = tk.BooleanVar(value=self.config.skip_path_dialog)
        ctk.CTkCheckBox(
            main_frame,
            text="记住我的选择",
            variable=self.remember_var,
            font=ctk.CTkFont(size=13)
        ).pack(pady=(0, 15))
        
        # 预览 - 使用滚动文本框
        preview_frame = ctk.CTkFrame(main_frame)
        preview_frame.pack(fill="both", expand=True, pady=(0, 15))
        
        ctk.CTkLabel(
            preview_frame,
            text="目录结构预览：",
            font=ctk.CTkFont(size=14, weight="bold")
        ).pack(anchor="w", padx=15, pady=(10, 5))
        
        self.preview_text = ctk.CTkTextbox(
            preview_frame,
            font=ctk.CTkFont(family="Consolas", size=12),
            height=100
        )
        self.preview_text.pack(fill="both", expand=True, padx=15, pady=(0, 10))
        self._update_preview()
        
        # 按钮框架 - 固定在底部
        button_frame = ctk.CTkFrame(main_frame, fg_color="transparent", height=40)
        button_frame.pack(fill="x", side="bottom")
        button_frame.pack_propagate(False)
        
        # 按钮容器
        btn_container = ctk.CTkFrame(button_frame, fg_color="transparent")
        btn_container.place(relx=1.0, rely=0.5, anchor="e")
        
        ctk.CTkButton(
            btn_container,
            text="取消",
            width=100,
            fg_color="#E0E0E0",
            hover_color="#BDBDBD",
            text_color="#424242",
            command=self._on_cancel
        ).pack(side="left", padx=(0, 10))
        
        ctk.CTkButton(
            btn_container,
            text="确定",
            width=100,
            command=self._on_ok
        ).pack(side="left")
        
    def _browse_folder(self):
        """浏览文件夹"""
        folder = filedialog.askdirectory(initialdir=self.path_var.get())
        if folder:
            self.path_var.set(folder)
            self._update_preview()
            
    def _update_preview(self):
        """更新预览"""
        path = os.path.join(self.path_var.get(), self.name_var.get())
        preview = f"""安装路径：{path}

目录结构：
{self.name_var.get()}/
├── XRK-Yunzai/     (项目文件)
├── software/       (软件包)
│   └── NapCat/    (NapCat程序)
└── logs/          (日志文件)"""
        
        self.preview_text.delete("1.0", "end")
        self.preview_text.insert("1.0", preview)
        
    def _on_ok(self):
        """确定"""
        if not self.name_var.get().strip():
            msg = messagebox.showwarning("提示", "请输入文件夹名称", parent=self)
            return
            
        self.result = {
            'parent_path': self.path_var.get(),
            'dir_name': self.name_var.get().strip(),
            'skip_dialog': self.remember_var.get()
        }
        self.destroy()
        
    def _on_cancel(self):
        """取消"""
        self.result = None
        self.destroy()

class ProgressDialog(ctk.CTkToplevel):
    """进度对话框"""
    def __init__(self, parent, title: str, description: str):
        super().__init__(parent)
        self.parent = parent
        
        self.title(title)
        self.geometry("500x200")
        self.resizable(False, False)
        
        # 设置模态
        self.transient(parent)
        self.grab_set()
        
        # 创建界面
        self._create_widgets(description)
        
        # 居中窗口
        center_window(self)
        
        self.cancelled = False
        self.protocol("WM_DELETE_WINDOW", self._on_cancel)
        
    def _create_widgets(self, description: str):
        """创建控件"""
        main_frame = ctk.CTkFrame(self)
        main_frame.pack(fill="both", expand=True, padx=20, pady=20)
        
        # 描述
        self.desc_label = ctk.CTkLabel(
            main_frame,
            text=description,
            font=ctk.CTkFont(size=14)
        )
        self.desc_label.pack(pady=(10, 20))
        
        # 进度条
        self.progress = ctk.CTkProgressBar(main_frame)
        self.progress.pack(fill="x", padx=20)
        self.progress.set(0)
        
        # 详情
        self.detail_label = ctk.CTkLabel(
            main_frame,
            text="准备中...",
            font=ctk.CTkFont(size=12),
            text_color=COLORS["text_secondary"]
        )
        self.detail_label.pack(pady=(10, 0))
        
    def update_progress(self, value: float, detail: str = ""):
        """更新进度"""
        if not self.cancelled:
            self.progress.set(value / 100)
            if detail:
                self.detail_label.configure(text=detail)
            self.update()
            
    def _on_cancel(self):
        """取消"""
        self.cancelled = True
        self.destroy()

class YunzaiManager(ctk.CTk):
    """葵崽专属管理器主窗口 - 简洁版"""
    def __init__(self):
        super().__init__()
        
        # 初始化管理器
        self.config_manager = ConfigManager()
        self.config = self.config_manager.load()
        self.path_manager = PathManager()
        self.env_checker = EnvironmentChecker()
        self.download_manager = DownloadManager(
            self.config.use_github_proxy,
            self.config.selected_github_proxy
        )
        
        # 设置窗口
        self.title(f"{APP_NAME} v{APP_VERSION}")
        self.geometry("1200x800")
        self.minsize(1000, 700)
        
        # 创建UI
        self._create_ui()
        
        # 初始化
        self._initialize()
        
        # 居中窗口
        center_window(self)
        
        # 保存配置
        self.protocol("WM_DELETE_WINDOW", self._on_closing)
        
    def _on_closing(self):
        """关闭窗口"""
        self.config_manager.save(self.config)
        self.destroy()
        
    def _create_ui(self):
        """创建UI界面 - 简洁布局"""
        # 顶部栏
        top_frame = ctk.CTkFrame(self, height=60)
        top_frame.pack(fill="x")
        top_frame.pack_propagate(False)
        
        # 标题
        title_label = ctk.CTkLabel(
            top_frame,
            text=APP_NAME,
            font=ctk.CTkFont(size=24, weight="bold")
        )
        title_label.pack(side="left", padx=30)
        
        # 顶部按钮
        top_buttons = ctk.CTkFrame(top_frame)
        top_buttons.pack(side="right", padx=30)
        
        ctk.CTkButton(
            top_buttons,
            text="刷新状态",
            width=100,
            command=self._refresh_environment
        ).pack(side="left", padx=5)
        
        ctk.CTkButton(
            top_buttons,
            text="更改路径",
            width=100,
            command=self._change_path
        ).pack(side="left", padx=5)
        
        ctk.CTkButton(
            top_buttons,
            text="代理设置",
            width=100,
            command=self._show_proxy_settings
        ).pack(side="left", padx=5)
        
        # 路径信息栏
        path_frame = ctk.CTkFrame(self, height=40, fg_color=COLORS["bg"])
        path_frame.pack(fill="x")
        path_frame.pack_propagate(False)
        
        self.path_label = ctk.CTkLabel(
            path_frame,
            text="当前路径: 未设置",
            font=ctk.CTkFont(size=13)
        )
        self.path_label.pack(side="left", padx=30)
        
        # 主内容区
        main_frame = ctk.CTkFrame(self)
        main_frame.pack(fill="both", expand=True, padx=20, pady=20)
        
        # 左右分栏
        left_frame = ctk.CTkFrame(main_frame)
        left_frame.pack(side="left", fill="both", expand=True, padx=(0, 10))
        
        right_frame = ctk.CTkFrame(main_frame)
        right_frame.pack(side="right", fill="both", expand=True, padx=(10, 0))
        
        # 左侧 - 环境状态
        self._create_env_status(left_frame)
        
        # 右侧 - 操作面板
        self._create_operation_panel(right_frame)
        
        # 底部状态栏
        status_frame = ctk.CTkFrame(self, height=30)
        status_frame.pack(fill="x")
        status_frame.pack_propagate(False)
        
        self.status_label = ctk.CTkLabel(
            status_frame,
            text="就绪",
            font=ctk.CTkFont(size=12)
        )
        self.status_label.pack(side="left", padx=30)
        
    def _create_env_status(self, parent):
        """创建环境状态面板"""
        # 标题
        ctk.CTkLabel(
            parent,
            text="环境状态",
            font=ctk.CTkFont(size=18, weight="bold")
        ).pack(pady=(10, 15))
        
        # 状态显示框
        self.status_text = ctk.CTkTextbox(
            parent,
            font=ctk.CTkFont(family="Consolas", size=13)
        )
        self.status_text.pack(fill="both", expand=True, padx=15, pady=(0, 15))
        
    def _create_operation_panel(self, parent):
        """创建操作面板"""
        # 标题
        ctk.CTkLabel(
            parent,
            text="操作面板",
            font=ctk.CTkFont(size=18, weight="bold")
        ).pack(pady=(10, 15))
        
        # 滚动框架
        scroll_frame = ctk.CTkScrollableFrame(parent)
        scroll_frame.pack(fill="both", expand=True, padx=15, pady=(0, 15))
        
        # 环境安装
        self._create_section(scroll_frame, "环境安装", [
            ("安装 Node.js", self._install_nodejs),
            ("安装 pnpm", self._install_pnpm),
            ("安装 Git", self._install_git),
            ("安装 Redis", self._install_redis),
            ("安装 FFmpeg", self._install_ffmpeg),
        ])
        
        # 项目操作
        self._create_section(scroll_frame, "项目操作", [
            ("克隆项目", self._clone_project),
            ("安装 NapCat", self._install_napcat),
            ("启动 NapCat", self._start_napcat),
            ("启动葵崽", self._start_yunzai),
        ])
        
        # 快速操作
        self._create_section(scroll_frame, "快速操作", [
            ("全新安装", self._install_all),
            ("打开项目目录", self._open_project_dir),
            ("打开工作目录", self._open_workspace_dir),
            ("查看日志", self._view_log),
        ])
        
    def _create_section(self, parent, title: str, buttons: List[Tuple[str, Callable]]):
        """创建操作区段"""
        # 区段框架
        section_frame = ctk.CTkFrame(parent)
        section_frame.pack(fill="x", pady=(0, 15))
        
        # 标题
        ctk.CTkLabel(
            section_frame,
            text=title,
            font=ctk.CTkFont(size=15, weight="bold")
        ).pack(anchor="w", padx=15, pady=(10, 10))
        
        # 按钮
        for text, command in buttons:
            btn = ctk.CTkButton(
                section_frame,
                text=text,
                command=command,
                height=35,
                font=ctk.CTkFont(size=13)
            )
            btn.pack(fill="x", padx=15, pady=(0, 8))
            
            # 特殊按钮样式
            if "全新安装" in text:
                btn.configure(fg_color=COLORS["primary"], height=40)
            elif "启动" in text:
                btn.configure(fg_color=COLORS["success"])
            
        # 底部间距
        ctk.CTkFrame(section_frame, height=5, fg_color="transparent").pack()
        
    def _initialize(self):
        """初始化"""
        if not self.config.skip_path_dialog or not self.config.last_install_path:
            self.after(100, self._show_path_dialog)
        else:
            self.path_manager.initialize(
                self.config.last_install_path,
                self.config.yunzai_dir_name
            )
            self._update_path_label()
            self._refresh_environment()
            
    def _show_path_dialog(self):
        """显示路径选择对话框"""
        dialog = PathSelectionDialog(self, self.config)
        self.wait_window(dialog)
        
        if dialog.result:
            self.config.last_install_path = dialog.result['parent_path']
            self.config.yunzai_dir_name = dialog.result['dir_name']
            self.config.skip_path_dialog = dialog.result['skip_dialog']
            
            self.path_manager.initialize(
                self.config.last_install_path,
                self.config.yunzai_dir_name
            )
            
            self.config_manager.save(self.config)
            self._update_path_label()
            self._refresh_environment()
        else:
            self.quit()
            
    def _update_path_label(self):
        """更新路径标签"""
        self.path_label.configure(text=f"当前路径: {self.path_manager.yunzai_dir}")
        
    def _update_status(self, message: str):
        """更新状态栏"""
        self.status_label.configure(text=message)
        self.update()
        
    def _log(self, message: str):
        """写入日志"""
        timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
        log_entry = f"[{timestamp}] {message}\n"
        
        # 写入日志文件
        if hasattr(self.path_manager, 'log_file'):
            try:
                with open(self.path_manager.log_file, 'a', encoding='utf-8') as f:
                    f.write(log_entry)
            except:
                pass
                
    def _refresh_environment(self):
        """刷新环境状态"""
        self._update_status("正在检测环境...")
        
        def check():
            status_lines = ["=== 环境检测结果 ===\n"]
            
            # Node.js
            node_ok, node_ver = self.env_checker.check_nodejs()
            status_lines.append(f"{'✓' if node_ok else '✗'} Node.js: {node_ver if node_ok else '未安装'}")
            
            # pnpm
            pnpm_ok = self.env_checker.check_pnpm()
            status_lines.append(f"{'✓' if pnpm_ok else '✗'} pnpm: {'已安装' if pnpm_ok else '未安装'}")
            
            # Git
            git_ok = self.env_checker.check_git()
            status_lines.append(f"{'✓' if git_ok else '✗'} Git: {'已安装' if git_ok else '未安装'}")
            
            # Redis
            redis_ok = self.env_checker.check_redis()
            status_lines.append(f"{'✓' if redis_ok else '✗'} Redis: {'已安装' if redis_ok else '未安装'}")
            
            # FFmpeg
            ffmpeg_ok = self.env_checker.check_ffmpeg()
            status_lines.append(f"{'✓' if ffmpeg_ok else '✗'} FFmpeg: {'已安装' if ffmpeg_ok else '未安装'}")
            
            # NapCat
            napcat_ok = self.env_checker.check_napcat(self.path_manager.napcat_path)
            status_lines.append(f"{'✓' if napcat_ok else '✗'} NapCat: {'已安装' if napcat_ok else '未安装'}")
            
            # 项目
            project_ok = self.env_checker.check_project(self.path_manager.project_path)
            status_lines.append(f"{'✓' if project_ok else '✗'} XRK-Yunzai: {'已克隆' if project_ok else '未克隆'}")
            
            # 更新UI
            self.after(0, lambda: self._update_status_display("\n".join(status_lines)))
            self.after(0, lambda: self._update_status("检测完成"))
            
        threading.Thread(target=check, daemon=True).start()
        
    def _update_status_display(self, text: str):
        """更新状态显示"""
        self.status_text.delete("1.0", "end")
        self.status_text.insert("1.0", text)
        
    def _change_path(self):
        """更改路径"""
        dialog = PathSelectionDialog(self, self.config)
        self.wait_window(dialog)
        
        if dialog.result:
            self.config.last_install_path = dialog.result['parent_path']
            self.config.yunzai_dir_name = dialog.result['dir_name']
            self.config.skip_path_dialog = dialog.result['skip_dialog']
            
            self.path_manager.initialize(
                self.config.last_install_path,
                self.config.yunzai_dir_name
            )
            
            self.config_manager.save(self.config)
            self._update_path_label()
            self._refresh_environment()
            
    def _show_proxy_settings(self):
        """显示代理设置"""
        # 简单的代理设置对话框
        dialog = ctk.CTkToplevel(self)
        dialog.title("代理设置")
        dialog.geometry("400x300")
        dialog.transient(self)
        dialog.grab_set()
        
        # 居中
        center_window(dialog)
        
        # 内容
        frame = ctk.CTkFrame(dialog)
        frame.pack(fill="both", expand=True, padx=20, pady=20)
        
        ctk.CTkLabel(
            frame,
            text="GitHub代理设置",
            font=ctk.CTkFont(size=18, weight="bold")
        ).pack(pady=(0, 20))
        
        # 启用代理
        use_proxy_var = tk.BooleanVar(value=self.config.use_github_proxy)
        ctk.CTkCheckBox(
            frame,
            text="启用GitHub代理",
            variable=use_proxy_var,
            font=ctk.CTkFont(size=14)
        ).pack(pady=(0, 15))
        
        # 代理选择
        proxy_var = tk.StringVar(value=self.config.selected_github_proxy)
        ctk.CTkLabel(frame, text="选择代理：").pack(anchor="w", pady=(0, 5))
        
        for proxy in GITHUB_PROXIES:
            ctk.CTkRadioButton(
                frame,
                text=proxy["name"],
                variable=proxy_var,
                value=proxy["url"]
            ).pack(anchor="w", padx=20, pady=2)
            
        # 按钮
        button_frame = ctk.CTkFrame(frame, fg_color="transparent")
        button_frame.pack(fill="x", pady=(20, 0))
        
        def save():
            self.config.use_github_proxy = use_proxy_var.get()
            self.config.selected_github_proxy = proxy_var.get()
            self.download_manager.use_proxy = self.config.use_github_proxy
            self.download_manager.proxy_url = self.config.selected_github_proxy
            self.config_manager.save(self.config)
            dialog.destroy()
            
            # 居中显示消息框
            msg = tk.Toplevel()
            messagebox.showinfo("成功", "代理设置已保存", parent=self)
            
        ctk.CTkButton(
            button_frame,
            text="保存",
            command=save
        ).pack(side="right", padx=(10, 0))
        
        ctk.CTkButton(
            button_frame,
            text="取消",
            fg_color="#E0E0E0",
            hover_color="#BDBDBD",
            text_color="#424242",
            command=dialog.destroy
        ).pack(side="right")
        
    def _run_in_thread(self, func: Callable, *args):
        """在线程中运行函数"""
        def run():
            try:
                func(*args)
            except Exception as e:
                self.after(0, lambda: messagebox.showerror("错误", str(e), parent=self))
                self._log(f"错误: {e}")
            finally:
                self._refresh_environment()
                
        threading.Thread(target=run, daemon=True).start()
        
    def _download_file(self, url: str, dest: str, desc: str) -> bool:
        """下载文件"""
        progress = ProgressDialog(self, "下载中", f"正在下载{desc}")
        
        def callback(percent, downloaded, total):
            mb_down = downloaded / 1024 / 1024
            mb_total = total / 1024 / 1024
            detail = f"{mb_down:.1f} MB / {mb_total:.1f} MB ({percent:.1f}%)"
            progress.update_progress(percent, detail)
            
        success = False
        def download():
            nonlocal success
            success = self.download_manager.download_with_progress(url, dest, callback)
            progress.destroy()
            
        thread = threading.Thread(target=download)
        thread.start()
        
        self.wait_window(progress)
        thread.join(timeout=1)
        
        return success
        
    def _install_nodejs(self):
        """安装Node.js"""
        self._run_in_thread(self._do_install_nodejs)
        
    def _do_install_nodejs(self):
        """执行Node.js安装"""
        if self.env_checker.check_nodejs()[0]:
            if not messagebox.askyesno("确认", "Node.js已安装，是否重新安装？", parent=self):
                return
                
        self._update_status("正在下载Node.js...")
        self._log("开始安装Node.js")
        
        installer = os.path.join(self.path_manager.software_path, "node.msi")
        url = DOWNLOAD_URLS["nodejs"][SYSTEM_ARCH]
        
        if not self._download_file(url, installer, "Node.js"):
            messagebox.showerror("错误", "下载Node.js失败", parent=self)
            return
            
        self._update_status("正在安装Node.js...")
        
        # 静默安装
        cmd = f'msiexec /i "{installer}" /qn /norestart'
        result = subprocess.run(cmd, shell=True, capture_output=True)
        
        if result.returncode != 0:
            # 交互式安装
            subprocess.run([installer], shell=True)
            
        self._log("Node.js安装完成")
        messagebox.showinfo("成功", "Node.js安装完成", parent=self)
        
    def _install_pnpm(self):
        """安装pnpm"""
        self._run_in_thread(self._do_install_pnpm)
        
    def _do_install_pnpm(self):
        """执行pnpm安装"""
        if not self.env_checker.check_nodejs()[0]:
            messagebox.showwarning("提示", "请先安装Node.js", parent=self)
            return
            
        if self.env_checker.check_pnpm():
            if not messagebox.askyesno("确认", "pnpm已安装，是否重新安装？", parent=self):
                return
                
        self._update_status("正在安装pnpm...")
        self._log("开始安装pnpm")
        
        # 卸载旧版本
        subprocess.run(["npm", "rm", "-g", "pnpm"], shell=True, capture_output=True)
        
        # 安装新版本
        result = subprocess.run(
            ["npm", "install", "-g", "pnpm@latest"],
            shell=True,
            capture_output=True,
            text=True
        )
        
        if result.returncode == 0:
            self._log("pnpm安装完成")
            messagebox.showinfo("成功", "pnpm安装完成", parent=self)
        else:
            messagebox.showerror("错误", "pnpm安装失败", parent=self)
            
    def _install_git(self):
        """安装Git"""
        self._run_in_thread(self._do_install_git)
        
    def _do_install_git(self):
        """执行Git安装"""
        if self.env_checker.check_git():
            if not messagebox.askyesno("确认", "Git已安装，是否重新安装？", parent=self):
                return
                
        self._update_status("正在下载Git...")
        self._log("开始安装Git")
        
        installer = os.path.join(self.path_manager.software_path, "git.exe")
        url = DOWNLOAD_URLS["git"][SYSTEM_ARCH]
        
        if not self._download_file(url, installer, "Git"):
            messagebox.showerror("错误", "下载Git失败", parent=self)
            return
            
        self._update_status("正在安装Git...")
        
        # 静默安装
        cmd = f'"{installer}" /VERYSILENT /NORESTART'
        result = subprocess.run(cmd, shell=True, capture_output=True)
        
        if result.returncode != 0:
            # 交互式安装
            subprocess.run([installer], shell=True)
            
        self._log("Git安装完成")
        messagebox.showinfo("成功", "Git安装完成", parent=self)
        
    def _install_redis(self):
        """安装Redis"""
        self._run_in_thread(self._do_install_redis)
        
    def _do_install_redis(self):
        """执行Redis安装"""
        if self.env_checker.check_redis():
            if not messagebox.askyesno("确认", "Redis已安装，是否重新安装？", parent=self):
                return
                
        self._update_status("正在下载Redis...")
        self._log("开始安装Redis")
        
        installer = os.path.join(self.path_manager.software_path, "redis.msi")
        url = DOWNLOAD_URLS["redis"][SYSTEM_ARCH]
        
        if not self._download_file(url, installer, "Redis"):
            messagebox.showerror("错误", "下载Redis失败", parent=self)
            return
            
        self._update_status("正在安装Redis...")
        
        # 静默安装
        cmd = f'msiexec /i "{installer}" /qn /norestart'
        result = subprocess.run(cmd, shell=True, capture_output=True)
        
        if result.returncode != 0:
            # 交互式安装
            subprocess.run([installer], shell=True)
            
        self._log("Redis安装完成")
        messagebox.showinfo("成功", "Redis安装完成", parent=self)
        
    def _install_ffmpeg(self):
        """安装FFmpeg"""
        self._run_in_thread(self._do_install_ffmpeg)
        
    def _do_install_ffmpeg(self):
        """执行FFmpeg安装"""
        if self.env_checker.check_ffmpeg():
            if not messagebox.askyesno("确认", "FFmpeg已安装，是否重新安装？", parent=self):
                return
                
        self._update_status("正在下载FFmpeg...")
        self._log("开始安装FFmpeg")
        
        zip_file = os.path.join(self.path_manager.software_path, "ffmpeg.zip")
        url = DOWNLOAD_URLS["ffmpeg"][SYSTEM_ARCH]
        
        if not self._download_file(url, zip_file, "FFmpeg"):
            messagebox.showerror("错误", "下载FFmpeg失败", parent=self)
            return
            
        self._update_status("正在解压FFmpeg...")
        
        # 解压
        extract_path = os.path.join(self.path_manager.software_path, "ffmpeg")
        if os.path.exists(extract_path):
            shutil.rmtree(extract_path)
            
        with zipfile.ZipFile(zip_file, 'r') as zf:
            zf.extractall(extract_path)
            
        # 查找bin目录
        bin_path = None
        for root, dirs, files in os.walk(extract_path):
            if 'bin' in dirs:
                bin_path = os.path.join(root, 'bin')
                break
                
        if not bin_path:
            messagebox.showerror("错误", "未找到FFmpeg bin目录", parent=self)
            return
            
        # 添加到环境变量
        try:
            import winreg
            key = winreg.OpenKey(winreg.HKEY_CURRENT_USER, 
                               r"Environment", 0, winreg.KEY_ALL_ACCESS)
            
            try:
                path_value, _ = winreg.QueryValueEx(key, "Path")
            except:
                path_value = ""
                
            if bin_path not in path_value:
                new_path = f"{path_value};{bin_path}" if path_value else bin_path
                winreg.SetValueEx(key, "Path", 0, winreg.REG_EXPAND_SZ, new_path)
                
            winreg.CloseKey(key)
            
            # 广播更改
            import ctypes
            HWND_BROADCAST = 0xFFFF
            WM_SETTINGCHANGE = 0x1A
            ctypes.windll.user32.SendMessageW(HWND_BROADCAST, WM_SETTINGCHANGE, 0, "Environment")
            
            self._log("FFmpeg安装完成")
            messagebox.showinfo("成功", "FFmpeg已安装并添加到环境变量", parent=self)
        except:
            messagebox.showinfo("提示", f"FFmpeg已解压到：{bin_path}\n请手动添加到环境变量", parent=self)
            
    def _clone_project(self):
        """克隆项目"""
        self._run_in_thread(self._do_clone_project)
        
    def _do_clone_project(self):
        """执行项目克隆"""
        if os.path.exists(self.path_manager.project_path):
            if not messagebox.askyesno("确认", "项目已存在，是否删除并重新克隆？", parent=self):
                return
            shutil.rmtree(self.path_manager.project_path)
            
        if not self.env_checker.check_git():
            messagebox.showwarning("提示", "请先安装Git", parent=self)
            return
            
        self._update_status("正在克隆项目...")
        self._log("开始克隆XRK-Yunzai项目")
        
        # 配置git
        subprocess.run(["git", "config", "--global", "core.longpaths", "true"], 
                      capture_output=True, shell=True)
        
        # 克隆命令
        os.makedirs(os.path.dirname(self.path_manager.project_path), exist_ok=True)
        
        # 尝试GitCode
        result = subprocess.run(
            ["git", "clone", "--depth=1", 
             "https://gitcode.com/Xrkseek/XRK-Yunzai.git",
             self.path_manager.project_path],
            capture_output=True,
            text=True,
            shell=True,
            cwd=self.path_manager.yunzai_dir
        )
        
        if result.returncode != 0:
            # 尝试GitHub
            self._update_status("从GitHub克隆...")
            url = "https://github.com/Xrkseek/XRK-Yunzai.git"
            if self.config.use_github_proxy:
                url = self.config.selected_github_proxy + url
                
            result = subprocess.run(
                ["git", "clone", "--depth=1", url, self.path_manager.project_path],
                capture_output=True,
                text=True,
                shell=True,
                cwd=self.path_manager.yunzai_dir
            )
            
        if result.returncode == 0:
            # 安装依赖
            self._update_status("正在安装项目依赖...")
            
            # 检查pnpm
            if self.env_checker.check_pnpm():
                subprocess.run(
                    ["pnpm", "install", "--registry", "https://registry.npmmirror.com"],
                    cwd=self.path_manager.project_path,
                    shell=True
                )
            
            self._log("项目克隆完成")
            messagebox.showinfo("成功", "项目克隆完成", parent=self)
        else:
            messagebox.showerror("错误", "项目克隆失败", parent=self)
            
    def _install_napcat(self):
        """安装NapCat"""
        self._run_in_thread(self._do_install_napcat)
        
    def _do_install_napcat(self):
        """执行NapCat安装"""
        self._update_status("正在下载NapCat...")
        self._log("开始安装NapCat")
        
        zip_file = os.path.join(self.path_manager.software_path, "napcat.zip")
        url = DOWNLOAD_URLS["napcat"][SYSTEM_ARCH]
        
        if not self._download_file(url, zip_file, "NapCat"):
            messagebox.showerror("错误", "下载NapCat失败", parent=self)
            return
            
        self._update_status("正在解压NapCat...")
        
        # 清空目标目录
        if os.path.exists(self.path_manager.napcat_path):
            shutil.rmtree(self.path_manager.napcat_path)
        os.makedirs(self.path_manager.napcat_path, exist_ok=True)
        
        # 解压
        with zipfile.ZipFile(zip_file, 'r') as zf:
            zf.extractall(self.path_manager.napcat_path)
            
        # 检查exe
        exe_path = os.path.join(self.path_manager.napcat_path, "NapCatWinBootMain.exe")
        if not os.path.exists(exe_path):
            messagebox.showerror("错误", "未找到NapCat启动程序", parent=self)
            return
            
        self._log("NapCat下载解压完成")
        messagebox.showinfo("成功", "NapCat已下载并解压完成！\n请使用'启动NapCat'按钮来启动程序。", parent=self)
        
    def _start_napcat(self):
        """启动NapCat"""
        exe_path = os.path.join(self.path_manager.napcat_path, "NapCatWinBootMain.exe")
        
        if not os.path.exists(exe_path):
            messagebox.showwarning("提示", "未找到NapCat程序，请先安装NapCat", parent=self)
            return
            
        try:
            import ctypes
            # 以管理员身份运行
            if ctypes.windll.shell32.IsUserAnAdmin():
                subprocess.Popen([exe_path], cwd=self.path_manager.napcat_path)
            else:
                ctypes.windll.shell32.ShellExecuteW(
                    None, "runas", exe_path, None, 
                    self.path_manager.napcat_path, 1
                )
                
            self._log("启动NapCat")
            messagebox.showinfo("提示", "NapCat启动中，请在弹出的窗口中完成安装和登录", parent=self)
        except Exception as e:
            messagebox.showerror("错误", f"启动NapCat失败：{e}", parent=self)
            
    def _start_yunzai(self):
        """启动葵崽"""
        if not os.path.exists(self.path_manager.project_path):
            messagebox.showwarning("提示", "请先克隆项目", parent=self)
            return
            
        app_js = os.path.join(self.path_manager.project_path, "app.js")
        if not os.path.exists(app_js):
            messagebox.showwarning("提示", "项目文件不完整，请重新克隆", parent=self)
            return
            
        try:
            # 创建启动脚本
            bat_file = os.path.join(self.path_manager.project_path, "start.bat")
            with open(bat_file, 'w', encoding='utf-8') as f:
                f.write('@echo off\n')
                f.write('title 葵崽 - XRK-Yunzai\n')
                f.write('chcp 65001>nul\n')
                f.write(f'cd /d "{self.path_manager.project_path}"\n')
                f.write('echo ========================================\n')
                f.write('echo     葵崽 XRK-Yunzai 启动中...\n')
                f.write('echo ========================================\n')
                f.write('echo.\n')
                f.write('node app.js\n')
                f.write('pause\n')
                
            # 启动
            subprocess.Popen(
                ['cmd', '/c', 'start', bat_file],
                shell=True
            )
            
            self._log("启动XRK-Yunzai")
            messagebox.showinfo("提示", "葵崽已启动，请查看命令行窗口", parent=self)
        except Exception as e:
            messagebox.showerror("错误", f"启动失败：{e}", parent=self)
            
    def _install_all(self):
        """全新安装"""
        if not messagebox.askyesno("确认", "将安装所有环境和项目，是否继续？", parent=self):
            return
            
        def install():
            steps = [
                ("Node.js", self._do_install_nodejs),
                ("pnpm", self._do_install_pnpm),
                ("Git", self._do_install_git),
                ("Redis", self._do_install_redis),
                ("FFmpeg", self._do_install_ffmpeg),
                ("NapCat", self._do_install_napcat),
                ("XRK-Yunzai", self._do_clone_project),
            ]
            
            for name, func in steps:
                self._update_status(f"正在安装 {name}...")
                try:
                    func()
                except Exception as e:
                    self.after(0, lambda: messagebox.showerror("错误", f"{name}安装失败：{e}", parent=self))
                    break
                    
            self.after(0, lambda: messagebox.showinfo("完成", "全新安装完成！", parent=self))
            
        threading.Thread(target=install, daemon=True).start()
        
    def _open_project_dir(self):
        """打开项目目录"""
        if os.path.exists(self.path_manager.project_path):
            os.startfile(self.path_manager.project_path)
        else:
            messagebox.showwarning("提示", "项目目录不存在", parent=self)
            
    def _open_workspace_dir(self):
        """打开工作目录"""
        if os.path.exists(self.path_manager.yunzai_dir):
            os.startfile(self.path_manager.yunzai_dir)
        else:
            messagebox.showwarning("提示", "工作目录不存在", parent=self)
            
    def _view_log(self):
        """查看日志"""
        if os.path.exists(self.path_manager.log_file):
            os.startfile(self.path_manager.log_file)
        else:
            messagebox.showinfo("提示", "暂无日志文件", parent=self)

def main():
    """主函数"""
    # 检查管理员权限
    if platform.system() == "Windows":
        import ctypes
        if not ctypes.windll.shell32.IsUserAnAdmin():
            print("请以管理员身份运行")
            messagebox.showwarning("提示", "请以管理员身份运行程序")
            ctypes.windll.shell32.ShellExecuteW(
                None, "runas", sys.executable, " ".join(sys.argv), None, 1
            )
            return
            
    # 启动应用
    try:
        app = YunzaiManager()
        app.mainloop()
    except Exception as e:
        traceback.print_exc()
        messagebox.showerror("错误", f"程序启动失败：{str(e)}")

if __name__ == "__main__":
    main()